package com.sharer.last.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sharer.last.enums.CommentTypeEnum;
import com.sharer.last.interfaces.TailRecursion;
import com.sharer.last.base.model.PageVO;
import com.sharer.last.base.support.QueryCondition;
import com.sharer.last.base.enums.BusinessExceptionCode;
import com.sharer.last.base.enums.ExceptionPrefixParamEnum;
import com.sharer.last.base.exception.BusinessException;
import com.sharer.last.model.dto.comment.CommentChangeDTO;
import com.sharer.last.model.dto.comment.CommentAddDTO;
import com.sharer.last.model.dto.comment.CommentQueryDTO;
import com.sharer.last.model.po.CommentPO;
import com.sharer.last.model.po.UserPO;
import com.sharer.last.model.vo.comment.CommentGeneralVO;
import com.sharer.last.model.vo.comment.CommentVO;
import com.sharer.last.service.BlogService;
import com.sharer.last.service.UserService;
import com.sharer.last.utils.BeanUtil;
import com.sharer.last.base.utils.CollectionUtil;
import com.sharer.last.base.utils.ConvertUtil;
import com.sharer.last.utils.IpAddressUtils;
import com.sharer.last.utils.QQInfoUtils;
import com.sharer.last.utils.TailInvokeUtil;
import com.sharer.last.utils.comment.CommentUtils;
import com.sharer.last.wrapper.CommentWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.sharer.last.mapper.CommentMapper;
import com.sharer.last.service.CommentService;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 博客评论业务层实现
 *
 * @ClassName CommentServiceImpl
 * @Author wangjin
 * @Date 2023/5/5 20:44
 * @Description
 * @Version 1.0
 */
@Service
public class CommentServiceImpl extends ServiceImpl<CommentMapper, CommentPO> implements CommentService {
    @Resource
    CommentMapper commentMapper;

    @Resource
    CommentWrapper commentWrapper;

    @Resource
    BlogService blogService;

    @Resource
    UserService userService;

    @Resource
    CommentUtils commentUtils;

    /**
     * 统计评论数量
     *
     * @param commentType 评论类型
     * @param relevantId  博客id
     * @param isPublished 是否公开
     * @return
     */
    @Override
    public long countByRelevantIdAndCommentType(Integer commentType, Long relevantId, Boolean isPublished) {
        LambdaQueryWrapper<CommentPO> qw = new LambdaQueryWrapper<>();
        qw.eq(CommentPO::getRelevantId, relevantId);
        qw.eq(CommentPO::getCommentType, commentType);
        if (isPublished != null) {
            qw.eq(CommentPO::getIsPublished, isPublished ? 1 : 0);
        }
        return this.count(qw);
    }

    /**
     * 获取指定id所有下级评论id集合（包括自己的id）
     *
     * @param id
     * @return
     */
    @Override
    public List<Long> getChildCommentIdsAndOwn(Long id) {
        if (ConvertUtil.toInt(id) < 1) {
            throw new BusinessException(BusinessExceptionCode.ID_IS_NULL, ExceptionPrefixParamEnum.COMMENT);
        }
        List<Long> idList = new ArrayList<>();
        idList.add(id);
        return this.getChildCommentIdListByIdsWithOwn(idList);
    }

    /**
     * 获取评论综合性数据
     *
     * @param commentQueryDTO
     * @return
     */
    @Override
    public CommentGeneralVO getCommentByPage(CommentQueryDTO commentQueryDTO) {
        Integer commentType = commentQueryDTO.getCommentType();
        Long    relevantId  = commentQueryDTO.getRelevantId();
        String  token       = commentQueryDTO.getToken();
        if (CommentTypeEnum.BLOG.getCode().equals(commentType)) {
            // 校验博客数据
            blogService.checkBlog(relevantId, token);
        }
        PageVO<CommentVO> commentVOPageVO = this.selectListByPage(commentQueryDTO);
        return CommentGeneralVO.builder().comments(commentVOPageVO)
                .allComment(this.countByRelevantIdAndCommentType(commentType, relevantId, null))
                .closeComment(this.countByRelevantIdAndCommentType(commentType, relevantId, false)).build();
    }

    /**
     * 查询评论数据及下级评论
     *
     * @param id
     * @return
     */
    @Override
    public CommentVO findById(Long id) {
        if (ConvertUtil.toInt(id) < 1) {
            throw new BusinessException(BusinessExceptionCode.ID_IS_NULL, ExceptionPrefixParamEnum.COMMENT);
        }
        CommentPO commentPO = this.getById(id);
        if (commentPO == null) {
            return null;
        }
        return commentWrapper.entityVO(commentPO);
    }

    /**
     * 指定类型（博客文章、关于我、友链）下的所有一级评论
     *
     * @param relevantId 关联id
     * @return
     */
    @Override
    public List<CommentPO> queryListByRelevantId(Long relevantId) {
        if (ConvertUtil.toInt(relevantId) < 1) {
            throw new BusinessException(BusinessExceptionCode.ID_IS_NULL, ExceptionPrefixParamEnum.COMMENT);
        }
        LambdaQueryWrapper<CommentPO> qw = new LambdaQueryWrapper<>();
        qw.eq(CommentPO::getRelevantId, relevantId);
        return this.list(qw);
    }

    /**
     * 查询指定评论的所有下级评论
     *
     * @param id 评论id
     * @return
     */
    @Override
    public List<CommentPO> queryListById(Long id) {
        if (ConvertUtil.toInt(id) < 1) {
            throw new BusinessException(BusinessExceptionCode.ID_IS_NULL, ExceptionPrefixParamEnum.COMMENT);
        }
        LambdaQueryWrapper<CommentPO> qw = new LambdaQueryWrapper<>();
        qw.eq(CommentPO::getParentCommentId, id);
        return this.list(qw);
    }

    /**
     * 查询指定评论的所有下级评论
     *
     * @param id 评论id
     * @return
     */
    @Override
    public List<CommentVO> selectListById(Long id) {
        List<CommentPO> commentPOS = this.queryListById(id);
        if (CollectionUtil.isNotEmpty(commentPOS)) {
            return commentWrapper.toVO(commentPOS);
        }
        return new ArrayList<>();
    }

    /**
     * 分页查询
     *
     * @param queryDTO 请求参数
     * @return
     */
    @Override
    public PageVO<CommentVO> selectListByPage(CommentQueryDTO queryDTO) {
        LambdaQueryWrapper<CommentPO> qw   = this.buildQueryWrapper(queryDTO);
        IPage<CommentPO>              page = this.page(QueryCondition.getPage(queryDTO.getPage()), qw);
        return commentWrapper.toPageVO(page);
    }

    /**
     * 创建评论
     *
     * @param dto
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createComment(CommentChangeDTO dto) {
        CommentPO commentPO = BeanUtil.copy(dto, CommentPO.class);
        if (!this.save(commentPO)) {
            throw new BusinessException(BusinessExceptionCode.DATA_INSERT_FAIL, ExceptionPrefixParamEnum.COMMENT);
        }
    }

    /**
     * 前端 新增评论
     *
     * @param comment
     * @param request
     */
    @Override
    public void createComment(CommentAddDTO comment, HttpServletRequest request) {
        Integer commentType  = comment.getCommentType();
        Long    relevantId   = comment.getRelevantId();
        String  token        = comment.getToken();
        Boolean adminComment = Optional.ofNullable(comment.getAdminComment()).orElse(false);
        Boolean notice       = Optional.ofNullable(comment.getNotice()).orElse(false);
        String  nickname     = comment.getNickname();
        if (CommentTypeEnum.BLOG.getCode().equals(commentType)) {
            // 校验博客数据
            blogService.checkBlog(relevantId, token);
        }
        CommentPO.CommentPOBuilder builder = CommentPO.builder()
                .isPublished(1)
                .commentType(commentType)
                .relevantId(relevantId)
                .isAdminComment(adminComment ? 1 : 0)
                .parentCommentId(comment.getParentCommentId())
                .isNotice(notice ? 1 : 0)
                .website(comment.getWebsite())
                .email(comment.getEmail())
                .content(comment.getContent())
                .nickname(nickname)
                .avatar(commentUtils.setCommentRandomAvatar(nickname))
                .ip(IpAddressUtils.getIpAddress(request))
                .createTime(LocalDateTime.now());
        // 校验是否为博主评论
        if (adminComment) {
            UserPO user = userService.getById(1L);
            builder.avatar(user.getAvatar())
                    .email(user.getEmail())
                    .nickname(user.getNickname())
                    .website("/");
        } else {
            try {
                // TODO qq昵称暂时获取不到
                if (QQInfoUtils.isQQNumber(nickname)) {
                    builder.qq(nickname)
                            .nickname(nickname)
                            .avatar(commentUtils.setCommentQQAvatar(nickname));
                }
            } catch (Exception e) {
                log.error("QQ获取昵称或头像失败", e);
            }
        }
        CommentPO commentPO = builder.build();
        boolean   save      = this.save(commentPO);
        if (!save) {
            throw new BusinessException(BusinessExceptionCode.DATA_INSERT_FAIL, ExceptionPrefixParamEnum.COMMENT);
        }
        // 校验是否需要发送邮件提醒
        CommentPO parentComment = this.getById(commentPO.getParentCommentId());
        commentUtils.judgeSendNotify(commentPO, !adminComment, parentComment);
    }

    /**
     * 更新评论
     *
     * @param dto
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateComment(CommentChangeDTO dto) {
        Long id = dto.getId();
        if (ConvertUtil.toInt(id) < 1) {
            throw new BusinessException(BusinessExceptionCode.ID_IS_NULL, ExceptionPrefixParamEnum.COMMENT);
        }
        // 查询评论
        CommentPO commentPO = this.getById(id);
        if (commentPO == null) {
            throw new BusinessException(BusinessExceptionCode.DATA_IS_EMPTY, ExceptionPrefixParamEnum.COMMENT);
        }
        CommentPO comment = BeanUtil.copy(dto, CommentPO.class);
        if (!this.updateById(comment)) {
            throw new BusinessException(BusinessExceptionCode.DATA_UPDATE_FAIL, ExceptionPrefixParamEnum.COMMENT);
        }
    }

    /**
     * 更新评论公开状态
     *
     * @param id
     * @param published
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateCommentPublishedById(Long id, Integer published) {
        List<Long> idList = new ArrayList<>();
        if (published == 0) {
            //如果是隐藏评论，则所有子评论都要修改成隐藏状态
            idList.addAll(this.getChildCommentIdsAndOwn(id));
        } else {
            idList.add(id);
        }
        List<CommentPO> commentPOList = new ArrayList<>();
        for (Long commentId : idList) {
            CommentPO commentPO = CommentPO.builder()
                    .id(commentId)
                    .isPublished(published)
                    .build();
            commentPOList.add(commentPO);
        }
        // 执行更新
        if (!this.updateBatchById(commentPOList)) {
            throw new BusinessException(BusinessExceptionCode.DATA_UPDATE_FAIL, ExceptionPrefixParamEnum.COMMENT);
        }
    }

    /**
     * 更新评论邮件提醒状态
     *
     * @param commentId
     * @param notice
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateCommentNoticeById(Long commentId, Integer notice) {
        // 查询数据是否存在
        CommentPO comment = this.getById(commentId);
        if (comment == null) {
            throw new BusinessException(BusinessExceptionCode.DATA_IS_EMPTY, ExceptionPrefixParamEnum.COMMENT);
        }
        CommentPO commentPO = new CommentPO();
        commentPO.setId(commentId);
        commentPO.setIsNotice(notice);
        if (!this.updateById(commentPO)) {
            throw new BusinessException(BusinessExceptionCode.DATA_UPDATE_FAIL, ExceptionPrefixParamEnum.COMMENT);
        }
    }

    /**
     * 删除评论
     *
     * @param id 评论id
     */
    @Override
    public void removeCommentById(Long id) {
        if (ConvertUtil.toInt(id) < 1) {
            throw new BusinessException(BusinessExceptionCode.ID_IS_NULL, ExceptionPrefixParamEnum.COMMENT);
        }
        // 判断评论是否存在
        CommentPO commentPO = this.getById(id);
        if (commentPO == null) {
            throw new BusinessException(BusinessExceptionCode.DATA_IS_EMPTY, ExceptionPrefixParamEnum.COMMENT);
        }
        // 删除评论包含下级评论
        List<Long> childCommentIds = this.getChildCommentIdsAndOwn(id);
        if (CollectionUtil.isNotEmpty(childCommentIds)) {
            if (!this.removeBatchByIds(childCommentIds)) {
                throw new BusinessException(BusinessExceptionCode.DATA_REMOVE_FAIL, ExceptionPrefixParamEnum.COMMENT);
            }
        }
    }

    /**
     * 删除指定关联id的数据
     *
     * @param relevantId 关联id
     * @return
     */
    @Override
    public void removeByRelevantId(Long relevantId) {
        // 查询指定类型（博客文章、关于我、友链）下的所有一级评论
        List<CommentPO> list   = this.queryListByRelevantId(relevantId);
        List<Long>      idList = list.stream().map(CommentPO::getId).collect(Collectors.toList());

        if (CollectionUtil.isNotEmpty(idList)) {
            // 获取下级评论id集合包括idList里的id
            List<Long> childCommentIds = this.getChildCommentIdListByIdsWithOwn(idList);
            if (CollectionUtil.isNotEmpty(childCommentIds)) {
                if (!this.removeBatchByIds(childCommentIds)) {
                    throw new BusinessException(BusinessExceptionCode.DATA_REMOVE_FAIL, ExceptionPrefixParamEnum.COMMENT);
                }
            }
        }

    }

    private LambdaQueryWrapper<CommentPO> buildQueryWrapper(CommentQueryDTO queryDTO) {
        LambdaQueryWrapper<CommentPO> qw = new LambdaQueryWrapper<>();
        // 评论类型
        Integer commentType = queryDTO.getCommentType();
        if (ConvertUtil.toInt(commentType) > 0) {
            qw.eq(CommentPO::getCommentType, commentType);
        }
        // 指定范围
        Long relevantId = queryDTO.getRelevantId();
        if (ConvertUtil.toInt(relevantId) > 0) {
            qw.eq(CommentPO::getRelevantId, relevantId);
        }
        qw.eq(CommentPO::getParentCommentId, -1);
        qw.orderByDesc(CommentPO::getCreateTime);
        return qw;
    }

    /**
     * 获取指定id集合下的所有下级评论id集合（包括自己）
     *
     * @param idList id集合
     * @return
     */
    private List<Long> getChildCommentIdListByIdsWithOwn(List<Long> idList) {
        TailRecursion<List<Long>> res = this.getChildCommentIdListById(idList, new ArrayList<>());
        return res.invoke();
    }

    /**
     * 根据id获取下级评论id集合（包括自己）
     *
     * @param idList   最终集合
     * @param children 临时递归子类id集合
     * @return
     */
    private TailRecursion<List<Long>> getChildCommentIdListById(List<Long> idList, List<Long> children) {
        List<Long> invokeList = new ArrayList<>();
        if (CollectionUtil.isEmpty(children)) {
            // 不能直接赋值=，即不能使用引用传递，因为idList在遍历的时候赋值了，导致invokeList值产生变化，导致出错
            invokeList.addAll(idList);
        } else {
            invokeList = children;
        }
        List<Long> tempList = new ArrayList<>();
        for (Long id : invokeList) {
            List<CommentVO> childList = this.selectListById(id);
            List<Long>      list      = childList.stream().map(CommentVO::getId).collect(Collectors.toList());
            idList.addAll(list);
            tempList.addAll(list);
        }
        if (CollectionUtil.isNotEmpty(tempList)) {
            return TailInvokeUtil.call(getChildCommentIdListById(idList, tempList));
        } else {
            return TailInvokeUtil.done(idList);
        }
    }
}
